/*-------------------------------------------------------------------------
 *
 * extensible.c
 *      Support for extensible node types
 *
 * Loadable modules can define what are in effect new types of nodes using
 * the routines in this file.  All such nodes are flagged T_ExtensibleNode,
 * with the extnodename field distinguishing the specific type.  Use
 * RegisterExtensibleNodeMethods to register a new type of extensible node,
 * and GetExtensibleNodeMethods to get information about a previously
 * registered type of extensible node.
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *      src/backend/nodes/extensible.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "nodes/extensible.h"
#include "utils/hsearch.h"

static HTAB *extensible_node_methods = NULL;
static HTAB *custom_scan_methods = NULL;

typedef struct
{
    char        extnodename[EXTNODENAME_MAX_LEN];
    const void *extnodemethods;
} ExtensibleNodeEntry;

/*
 * An internal function to register a new callback structure
 */
static void
RegisterExtensibleNodeEntry(HTAB **p_htable, const char *htable_label,
                            const char *extnodename,
                            const void *extnodemethods)
{
    ExtensibleNodeEntry *entry;
    bool        found;

    if (*p_htable == NULL)
    {
        HASHCTL        ctl;

        memset(&ctl, 0, sizeof(HASHCTL));
        ctl.keysize = EXTNODENAME_MAX_LEN;
        ctl.entrysize = sizeof(ExtensibleNodeEntry);

        *p_htable = hash_create(htable_label, 100, &ctl, HASH_ELEM);
    }

    if (strlen(extnodename) >= EXTNODENAME_MAX_LEN)
        elog(ERROR, "extensible node name is too long");

    entry = (ExtensibleNodeEntry *) hash_search(*p_htable,
                                                extnodename,
                                                HASH_ENTER, &found);
    if (found)
        ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_OBJECT),
                 errmsg("extensible node type \"%s\" already exists",
                        extnodename)));

    entry->extnodemethods = extnodemethods;
}

/*
 * Register a new type of extensible node.
 */
void
RegisterExtensibleNodeMethods(const ExtensibleNodeMethods *methods)
{
    RegisterExtensibleNodeEntry(&extensible_node_methods,
                                "Extensible Node Methods",
                                methods->extnodename,
                                methods);
}

/*
 * Register a new type of custom scan node
 */
void
RegisterCustomScanMethods(const CustomScanMethods *methods)
{
    RegisterExtensibleNodeEntry(&custom_scan_methods,
                                "Custom Scan Methods",
                                methods->CustomName,
                                methods);
}

/*
 * An internal routine to get an ExtensibleNodeEntry by the given identifier
 */
static const void *
GetExtensibleNodeEntry(HTAB *htable, const char *extnodename, bool missing_ok)
{
    ExtensibleNodeEntry *entry = NULL;

    if (htable != NULL)
        entry = (ExtensibleNodeEntry *) hash_search(htable,
                                                    extnodename,
                                                    HASH_FIND, NULL);
    if (!entry)
    {
        if (missing_ok)
            return NULL;
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("ExtensibleNodeMethods \"%s\" was not registered",
                        extnodename)));
    }

    return entry->extnodemethods;
}

/*
 * Get the methods for a given type of extensible node.
 */
const ExtensibleNodeMethods *
GetExtensibleNodeMethods(const char *extnodename, bool missing_ok)
{
    return (const ExtensibleNodeMethods *)
        GetExtensibleNodeEntry(extensible_node_methods,
                               extnodename,
                               missing_ok);
}

/*
 * Get the methods for a given name of CustomScanMethods
 */
const CustomScanMethods *
GetCustomScanMethods(const char *CustomName, bool missing_ok)
{
    return (const CustomScanMethods *)
        GetExtensibleNodeEntry(custom_scan_methods,
                               CustomName,
                               missing_ok);
}
